Feature: Code execution Middleware - #116
Draft
saharannaveen wants to merge 71 commits into
Draft
Conversation
…t System (redhat-data-and-ai#47) * FEAT: added deployment artifacts (redhat-data-and-ai#11) * FIX: rename LANGFUSE_HOST to LANGFUSE_BASE_URL (redhat-data-and-ai#16) * FEAT: Implement deep agent architecture with subagents and skills system - Add deep agent PoC with orchestrator pattern and subagent delegation - Implement analyst and publisher subagents with specialized skills - Create skills system (client-intake, bmi-report, email-formatter) - Refactor core backend with improved state management and storage - Reorganize test suite with agent-specific tests and LLM judge evaluation - Remove deprecated deployment configs and examples - Update dependencies and configuration files * fix .env.example * fix .env.example * fix ruff format * changed port to 5002 * fixed google creds * fixed google creds * add model in YAML frontmatter * restored revokation endpoint * FIX: resolve ssl.SSLError on Vertex streaming after MCP tool calls (redhat-data-and-ai#4) Disable HTTP connection pooling for Gemini clients to prevent stale TLS connections from causing ssl.SSLError("passed invalid argument") when streaming resumes after tool-call pauses. - agent.py: add httpx.Limits(max_keepalive_connections=0) to both model constructors so every request gets a fresh TLS handshake - Containerfile: consolidate RUN steps; source activate does not persist across Docker layers, use explicit --python path instead - pyproject.toml: relax requires-python from ==3.12.2 to >=3.12.2,<3.13 * fix: prevent main agent model from being clobbered by subagent loop The subagent configuration loop was reusing the 'model' variable, causing the main agent's LLM to be set to None if the last subagent .md file lacked a model field in its frontmatter. * fix: use tool_call_id for stable ToolMessage deduplication The id(msg) fallback was using Python memory addresses which don't survive checkpoint restore, causing duplicate messages to be sent to clients after restoration. For ToolMessages without .id, now use tool_call_id as a stable identifier. This ensures reliable deduplication across checkpoint restores while never dropping messages. * fix: clear venv when pyproject.toml changes to remove stale dependencies When dependencies are removed from pyproject.toml, pip install into an existing venv won't uninstall them. This causes dev/prod parity issues where code works locally (stale dep present) but fails in CI/production (fresh venv). Now clears the venv with --clear when the toml hash changes, ensuring no orphaned packages remain. * docs: clarify tool_calls name rewrite for SubAgentMiddleware SubAgentMiddleware wraps subagent invocations in a generic "task" tool call with the actual subagent name in args.subagent_type. This rewrite surfaces that name for the UI to display the specific subagent rather than the generic "task" wrapper. * refactor: consolidate to LANGFUSE_ENVIRONMENT for langfuse 4.x Langfuse 4.x removed trace_name and environment parameters from CallbackHandler(). The new API reads environment from the LANGFUSE_ENVIRONMENT env var automatically. Changes: - Replace LANGFUSE_TRACING_ENVIRONMENT with LANGFUSE_ENVIRONMENT in settings.py - Update feedback.py to let Langfuse() auto-read from env var - Update .env.example to use LANGFUSE_ENVIRONMENT - Update deployment yamls to use LANGFUSE_ENVIRONMENT This ensures traces show up in Langfuse with proper environment tags. * fix: restore structured logging in api.py for machine-parseable logs The logger uses structlog with JSONRenderer, which supports structured logging via kwargs. F-string formatting stringifies the data, making it unparseable. Changes: - Use logger.info("event_name", **data) instead of logger.info(f"event_name {data}") - Convert exception handlers to structured format - Remove redundant debug log lines - Fix logger.warn → logger.warning (proper method name) This ensures logs are machine-parseable JSON for better observability. * refactor: move asyncio import to top-level Importing asyncio inside a nested function makes it harder to see dependencies when reading top-level imports. While Python caches imports, moving it to the top improves code clarity. * fix: use unique thread IDs per test case to prevent state sharing Hardcoded thread IDs cause all test cases to share state through the MemorySaver checkpointer when tests run in parallel. Now each test case gets a unique thread ID by including the eval_id. Changes: - Update thread_id format: "agent-test" → "agent-test-{eval_id}" - Pass eval_id through run_agent_async and run_agent_sync - Apply fix to all three test files: analyst, publisher, orchestrator This ensures test isolation and prevents flaky test results from shared state. * fix: add helpful error message when system-prompt.md is missing If system-prompt.md is missing or unreadable, the code now raises AppException with a clear message indicating the expected file path instead of a raw FileNotFoundError. This helps users setting up the template for the first time. * security: use user cache directory for venv instead of /tmp Using /tmp for venv storage on shared hosts creates security risks: - /tmp is typically world-readable - Directory name is predictable (hash of root_dir) - Another user could pre-create the directory and inject malicious packages Changed to use ~/.cache/template-agent/venvs/ with user-only permissions (0o700) to prevent directory hijacking attacks on shared hosts. * feat: enhance Langfuse tracing with metadata and best practices Implemented Langfuse observability best practices following the official skill guidelines: **Baseline Requirements (now met):** - ✅ Model name - captured automatically by LangChain integration - ✅ Token usage - captured automatically by LangChain integration - ✅ Good trace names - set to "chat-response" for filtering - ✅ Trace input/output - LangChain handles automatically - ✅ Sensitive data masked - only user message logged, not all function args **Additional Context (newly added):** - session_id - enables conversation grouping in Sessions view - user_id - enables user filtering and cost attribution - tags - "template-agent", "chat" for per-feature analytics **Other improvements:** - Added Langfuse shutdown/flush on server shutdown to ensure all traces are sent - Set descriptive run_name for better trace discovery - Followed proper import order (Langfuse after env vars loaded) Traces now appear in Langfuse with: - User and session IDs for filtering - Descriptive names for searchability - Tags for feature-level analytics - Automatic model/token tracking Docs: https://langfuse.com/docs/integrations/langchain * fix: improve Langfuse initialization safety and test reliability **Issue 1: Module-level Langfuse client initialization** - Changed feedback.py to use lazy initialization via get_langfuse_client() - Prevents initialization failures if module is imported before env vars are loaded - Guarantees environment variables are available when client is created **Issue 2: Missing flush in tests** - Updated langfuse_client fixture to flush traces on teardown - Ensures test traces are sent to Langfuse before test cleanup - Uses yield pattern for proper fixture lifecycle management **Test fixes:** - Updated test_feedback.py to mock get_langfuse_client() instead of module-level client - Ensures tests work with new lazy initialization pattern Follows Langfuse best practices: - Import Langfuse AFTER loading environment variables - Call flush() before script/test exit Related to: #langfuse-review * fix: ensure single trace for all operations with Langfuse OTel context Uses start_as_current_observation context manager to wrap the entire agent invocation, ensuring all nested operations (tools from subagents and MCP) are properly nested under a single trace via OpenTelemetry context propagation. * fix: populate user_id and session_id in Langfuse with propagate_attributes Uses Langfuse SDK v4's propagate_attributes() context manager to properly set user_id, session_id, and tags on the trace, ensuring users and sessions are visible in Langfuse UI. trace_context now only contains trace_id. * refactor: remove redundant ai_call_id in favor of trace_id Removed ai_call_id throughout the codebase as it's redundant with Langfuse trace_id. This simplifies the code and reduces unnecessary identifiers in the response schema. * refactor: remove SSE prefix from stream logs * refactor: remove redundant ls_* metadata keys from RunnableConfig Removed ls_user_id, ls_session_id, and ls_tags from RunnableConfig metadata as they are redundant with propagate_attributes() which properly sets user_id, session_id, and tags for Langfuse. * fix: use correct LANGFUSE_TRACING_ENVIRONMENT variable Changed from LANGFUSE_ENVIRONMENT to LANGFUSE_TRACING_ENVIRONMENT as per Langfuse SDK v4 documentation. The environment is auto-read from the env var by the client and handler. Also commented out optional SSL config in .env.example with clarifying comment. * fix: remove model config from subagents in tests Subagents should inherit the model from parent agent in tests instead of trying to instantiate from model name string. This fixes ImportError for ChatVertexAI in orchestrator tests. * feat: support model configuration in subagents for tests Subagents can now specify their own model in YAML config (e.g., analyst using gemini-3.1-pro-preview). Creates proper ChatGoogleGenerativeAI instances matching production behavior. Falls back to default model if model creation fails or no model specified. * fix: resolve container startup failures in UBI9 image (redhat-data-and-ai#5) * fix: resolve container startup failures in UBI9 image - Containerfile: create /app/.cache with correct ownership so the non-root 'default' user can write sandbox venvs at runtime - backend.py (_base_python): prefer versioned python3.12 binary over the python3 symlink which points to system python 3.9 in UBI9 - backend.py (_ensure_venv): use /app/.cache inside containers instead of Path.home() which resolves to unwritable /opt/app-root/src/ - compose.yaml: change host port 5432→5433 to avoid conflict when running template-agent and template-mcp-server simultaneously * Remove PostgreSQL port mapping from compose.yaml Removed port mapping for PostgreSQL service. * fix: disable credentials in CORS to comply with wildcard origin spec Setting allow_credentials=True with allow_origins=["*"] violates the CORS specification and is rejected by browsers. Changed to allow_credentials=False to resolve this security constraint. Co-authored-by: mimran-khan <mimran-khan@users.noreply.github.com> * chore: empty commit Co-authored-by: NP-compete <NP-compete@users.noreply.github.com> * fix: persist user_id in checkpoint metadata and fix SQL injection in thread listing (redhat-data-and-ai#6) * fix: persist user_id in checkpoint metadata and fix SQL injection in thread listing Made-with: Cursor * fix: remove unused variable and apply ruff formatting Made-with: Cursor --------- Co-authored-by: Abhishek Shivkumar <ashivkum@redhat.com> * refactor: simplify Langfuse tracing implementation - Update Langfuse to 3.11.1 for LangChain 1.x compatibility - Remove complex trace context management (propagate_attributes, start_as_current_observation) - Simplify to single CallbackHandler in RunnableConfig - Remove duplicate metadata from config (already in configurable) - Remove unused legacy methods (_prepare_streaming_input_with_history, _save_final_conversation_state) - Update run_name to "template-agent" for consistency * chore: update subagent models to Gemini 2.5 - analyst: gemini-3.1-pro-preview -> gemini-2.5-pro - publisher: gemini-3.1-pro-preview -> gemini-2.5-flash * fix: add Langfuse user and session tracking - Update langfuse to 3.14.5 for better LangChain integration - Use langfuse_session_id and langfuse_user_id in configurable for proper tracking - Ensures user_id and session_id are captured in Langfuse traces * chore: update publisher model to gemini-2.5-pro * refactor: rename streaming methods for clarity and consistency Renamed methods across streaming package to improve code readability: - extract_from_message → extract_tool_call_id (tracker.py) - convert_to_simple_format → convert_message_to_api_format (converter.py) - get_message_id → extract_message_id (deduplicator.py) - filter_unseen → get_unseen_messages (deduplicator.py) - _handle_interrupts → _convert_interrupts_to_messages (handlers.py) - _extract_messages → _extract_and_deduplicate_messages (handlers.py) These changes make method names more descriptive and consistent with their actual behavior, improving maintainability and developer experience. * refactor: extract llm, mcp, and subagents modules from agent.py Improves code organization and testability by extracting specialized functionality into focused modules. Reduces agent.py from 219 to 118 lines. Adds comprehensive unit test suite with 51 tests covering all new modules. * feat: add JSON-based multi-MCP server configuration (redhat-data-and-ai#7) * feat: add JSON-based multi-MCP server configuration Load MCP server definitions from agent_config/mcp_servers.json with per-server auth, SSL, and timeout settings. Falls back to env-var config when the JSON file is absent. Connections run in parallel via asyncio.gather with fault isolation and tool-name deduplication. * refactor: rename mcp_servers.json to mcp.json Shorter, consistent filename for the multi-MCP config. Updated all references in mcp.py and test_mcp.py. * refactor: modernize message utilities and streaming module Renamed agent_utils.py to messages.py for clarity and removed legacy code patterns in favor of modern LangChain patterns. Simplified streaming components by removing defensive code no longer needed with LangChain 4.x and deepagents 0.4.12. Changes: - Rename agent_utils.py → messages.py (clearer naming) - Remove legacy additional_kwargs handling (unused in modern LangChain) - Remove custom message support (never used) - Move remove_tool_calls to streaming module (streaming-specific) - Simplify streaming tracker, deduplicator, and converter - Replace getattr/hasattr with direct attribute access - Add comprehensive test coverage (24 new tests for messages.py) - Update streaming tests (4 new tests for remove_tool_calls) Net reduction: 88 lines removed, all 128 tests passing * refactor: simplify exception handling with modern patterns Replaced over-engineered exception system with clean dataclass-based approach. Removed dead code and unused exception classes/error codes. Changes: - Flatten exceptions/ directory to single exceptions.py file - Replace Enum-based AppExceptionCode with frozen dataclass ErrorCode - Remove unused exception classes (ToolCallException, UnauthorizedException, ForbiddenException) - Remove unused error codes (E_001, E_002, E_004, E_005, E_006) - Keep only actively used error codes (E_003, E_007, E_008, E_009) - Update all imports from exceptions.exceptions to exceptions - Rename properties: error_code → code, response_code → status, detail_message → detail - Update test assertions to match new property names Result: 143 lines → 68 lines (52% reduction), all 128 tests passing * test: add comprehensive unit tests for exception handling Adds unit tests for ErrorCode dataclass, ErrorCodes constants, and AppException class covering immutability, property delegation, and error handling behavior. * refactor: simplify MCP config to use JSON only Remove environment variable fallback from MCP configuration, relying exclusively on agent_config/mcp.json for server definitions. This eliminates configuration duplication and simplifies the codebase. - Remove env var fallback logic from mcp.py (234→169 lines, 28% reduction) - Remove MCP_* environment variables from settings.py - Extract _handle_no_mcp_tools() helper for DRY error handling - Optimize deduplication loop and logging - Clean up deployment configs (configmap.yaml, deployment.yaml) - Update unit tests to remove env var fallback tests * refactor: simplify checkpointer to PostgreSQL-only with RESTful routes Remove in-memory checkpointer option and streamline to PostgreSQL-only implementation with improved API design and comprehensive test coverage. Core Changes: - Create checkpointer.py module with clean async context manager API - Remove storage.py and in-memory checkpointer code (~280 lines) - Rename initialize_database → initialize_checkpointer for consistency - Remove USE_INMEMORY_CHECKPOINTER setting from all configs API Improvements: - Implement RESTful URL pattern for routes - /v1/users/{user_id}/history/{thread_id} (was /v1/history/{thread_id}) - /v1/users/{user_id}/threads (was /v1/threads/{user_id}) - Make user_id mandatory in history endpoint for security - Fix threads endpoint row access (dict-like psycopg3 rows) - Add comprehensive logging with user_id context Tests: - Add test_checkpointer.py (8 tests) - Add test_history.py (9 tests, includes SQL injection protection) - Add test_threads.py (5 tests) - Update test_mcp.py (remove obsolete environment-based tests) - Total: 160 tests passing (+22) All changes maintain backward compatibility for PostgreSQL users while removing the complexity of dual-mode support. * refactor: optimize Langfuse integration with dependency injection Refactor Langfuse client initialization to use app state and dependency injection pattern, eliminating global variables and improving lifecycle management. Core Changes: - Initialize Langfuse client once in app.state during startup/shutdown - Remove global _langfuse_client variable from feedback.py - Inject client via FastAPI dependency injection Feedback Endpoint Improvements: - Fix /v1/feedback error (changed score() to create_score()) - Add proper error handling for Langfuse API failures - Add comprehensive logging (info on success, error with traceback) - Use to_thread() for non-blocking I/O operations - Return HTTP 503 when Langfuse not configured - Add test for error handling Manager Optimizations: - Pass Langfuse client from app.state to AgentManager - Create per-request CallbackHandler (required for trace isolation) - Inject shared client into handler to avoid recreating client - Only enable tracing callbacks when client is available Stream Endpoint: - Remove unnecessary response_class and responses parameters - Clean up unused imports (typing.Any, status) Tests: - Add test_feedback.py (7 tests) for feedback endpoint - Add tests for Langfuse client injection in AgentManager - Total: 169 tests passing (+9) Benefits: - Single Langfuse client instance (initialized once, not per-request) - Proper lifecycle management (startup/shutdown) - No global state, follows FastAPI patterns - Better error handling and observability - Non-blocking async execution for I/O operations * refactor: standardize UUIDs to hex format and add trace_id support - Standardize all UUID generation to hex format (32 chars, no hyphens) - Add trace_id field throughout streaming and history for better tracing - Simplify optional parameter handling in AgentManager - Extract helper functions in history route (is_subagent_checkpoint, convert_with_metadata, rewrite_task_tool_calls) - Optimize threads SQL query to use checkpoint_id instead of step (better performance) - Align feedback API with Langfuse naming (trace_id, name, value) - Fix bug: task tool calls now rewritten to subagent names in history API - Improve test suite: remove 8 bloated tests, add 5 meaningful tests - All 180 tests passing * fix: ensure trace_id and run_id are always included in streaming responses - Add run_id and trace_id from StreamContext to all streamed messages - Remove redundant conditional checks since context values are authoritative - Update tests to verify context metadata is always present - Fixes issue where trace_id was missing in /v1/stream responses * refactor: centralize agent_config with singleton pattern and eager loading - Create AgentConfig singleton class for centralized configuration management - Implement eager loading with lazy initialization for all agent configs - Move orchestrator config from system-prompt.md to orchestrator/main.md - Rename agents/ to subagents/ for clarity - Remove prompt.py and frontmatter.py, consolidate into agent_config.py - Pre-load and cache all configs at startup (orchestrator, subagents, MCP, skills) - Simplify agent.py, subagents.py, mcp.py, backend.py to use singleton - Add proper logging with lazy logger initialization - Remove unused path getter methods (get_subagents_dir, get_skills_dir, etc) - Make resolve_tools static method (pure utility function) - Use module-level constant _AGENT_CONFIG_DIR for default path Benefits: - Zero file I/O after initial load (all configs cached) - O(1) lookups for skills and configs - Fail-fast on startup for bad configs - Single source of truth for agent_config/ operations - Consistent structure for orchestrator and subagents * refactor: improve separation of concerns in agent architecture This commit refactors the agent system to properly separate utilities from orchestration logic and ensure clean boundaries between components. Core Changes: - Tools from main.md frontmatter now properly passed to create_deep_agent - Skills resolved eagerly during config loading (no longer a public API) - Subagents are fully isolated with no cross-agent awareness Agent Configuration (agent_config.py): - Skill resolution moved to config loading time (eager vs lazy) - _resolve_skill_paths is now private (was resolve_skills) - Skills scanned before orchestrator/subagents to enable resolution - Orchestrator and subagent configs include pre-resolved skill_paths Agent Creation (agent.py): - Extract tool_names from orchestrator config frontmatter - Resolve tools using agent_config.resolve_tools() - Pass resolved tools to create_deep_agent (was empty list) - Use pre-resolved skill_paths from config (no manual resolution) Subagent Loading (subagents.py): - Use pre-resolved skill_paths from config - Removed redundant skill resolution call Skills Refactoring: - client-intake: Removed all subagent/orchestration references - Changed from coordination guide to pure utility - Focuses on: input parsing, validation, unit conversion - coordination_flow.md → input_gathering.md (renamed, refactored) - edge_cases.md: Removed routing logic, validation-focused - Fixed convert_units.py usage (was showing wrong flag syntax) - Updated evals to test parsing/conversion, not delegation Orchestrator (main.md): - Added validate_email tool to tools list - Updated documentation to include email validation workflow - Added validation step in routing table and delegation flow - Updated mermaid diagram to show orchestrator tools Subagents: - publisher.md: Removed "upstream work" and "invoked" references - Description now input-focused, not workflow-aware - No knowledge of analyst or orchestration sequence Tests: - Added test_agent_config_skills.py for skill path resolution - Tests validate eager loading and singleton reset Principles Enforced: 1. Skills = context-agnostic utilities (no orchestration knowledge) 2. Subagents = isolated services (no cross-agent awareness) 3. Orchestrator = sole owner of workflow/routing logic (main.md) 4. Tools declared in frontmatter are passed through to agents * refactor: replace core/ anti-pattern with semantic package structure Eliminates the core/ directory dumping ground and organizes code by domain: - agent/ (factory, manager, llm, config) - agent creation and orchestration - infrastructure/ (backend, checkpointer, mcp, subagents) - supporting services - adapters/ (langchain) - external framework integration - streaming/ (handlers, deduplicator, tracker) - event processing - api/ (app, middleware, lifecycle, routes) - web service layer Splits large files into focused modules (agent_config.py → 3 files, api.py → 3 files) and fixes circular imports with lazy loading pattern in agent/__init__.py. Each module now includes comprehensive docstrings explaining its purpose and design rationale. * test: migrate test_exceptions.py to new import path Update import from template_agent.src.core.exceptions to template_agent.src.exceptions to match the new semantic package structure. ✅ All 14 tests passing ✅ Already optimal quality (focused tests, clear names, no mocks needed) * test: migrate test_streaming.py to streaming/ subdirectory Move tests/unit/test_streaming.py → tests/unit/streaming/test_streaming.py Update imports from template_agent.src.core.streaming to template_agent.src.streaming ✅ All 38 tests passing ✅ Already optimal quality (uses real objects with fake messages) * test: migrate test_messages.py to adapters/test_langchain.py Rename to reflect module purpose (LangChain message adapter) Move tests/unit/test_messages.py → tests/unit/adapters/test_langchain.py Update imports from template_agent.src.core.messages to template_agent.src.adapters.langchain ✅ All 24 tests passing ✅ Already optimal quality (uses real LangChain message objects) * test: migrate test_agent_config_skills.py to agent/config/test_config.py Move tests/unit/test_agent_config_skills.py → tests/unit/agent/config/test_config.py Update imports from template_agent.src.core.agent_config to template_agent.src.agent.config ✅ All 3 tests passing ✅ Already optimal quality (temp dirs, fake YAML, real AgentConfig, focused tests) * test: migrate test_manager.py to agent/test_manager.py Move tests/unit/test_manager.py → tests/unit/agent/test_manager.py Update imports from template_agent.src.core.manager to template_agent.src.agent.manager Update imports from template_agent.src.core.streaming to template_agent.src.streaming ✅ All 7 tests passing ✅ Already enhanced (uses real MessageDeduplicator/ToolCallTracker, not mocks) ✅ Only mocks Langfuse client (external service - appropriate) * test: migrate test_checkpointer.py to infrastructure/test_checkpointer.py Move tests/unit/test_checkpointer.py → tests/unit/infrastructure/test_checkpointer.py Update imports from template_agent.src.core.checkpointer to template_agent.src.infrastructure.checkpointer Update imports from template_agent.src.core.exceptions to template_agent.src.exceptions ✅ All 8 tests passing ✅ Mocks are appropriate (testing wrapper logic, not persistence) * test: migrate test_llm.py to agent/test_llm.py Move tests/unit/test_llm.py → tests/unit/agent/test_llm.py Update imports from template_agent.src.core.llm to template_agent.src.agent.llm ✅ All 8 tests passing ✅ Mocks are appropriate (external Google/Anthropic APIs) * test: migrate test_feedback.py to api/routes/test_feedback.py Move tests/unit/test_feedback.py → tests/unit/api/routes/test_feedback.py Update imports from template_agent.src.routes.feedback to template_agent.src.api.routes.agent.feedback ✅ All 6 tests passing ✅ Mocks are appropriate (Langfuse client - external service) * test: migrate test_threads.py to api/routes/test_threads.py Move tests/unit/test_threads.py → tests/unit/api/routes/test_threads.py Update imports from template_agent.src.routes.threads to template_agent.src.api.routes.memory.threads ✅ All 4 tests passing ✅ Mocks are appropriate (checkpointer - external database) * test: migrate test_history.py to api/routes/test_history.py Move tests/unit/test_history.py → tests/unit/api/routes/test_history.py Update imports from template_agent.src.routes.history to template_agent.src.api.routes.memory.history ✅ All 21 tests passing ✅ Mocks are appropriate (checkpointer - external database) * test: migrate test_google_creds.py to utils/test_google_creds.py Move tests/unit/test_google_creds.py → tests/unit/utils/test_google_creds.py ✅ All 8 tests passing ✅ Imports already correct (template_agent.utils.google_creds) ✅ Mocks are appropriate (Google service account credentials) * test: complete Phase 1 migration - rewrite test_subagents and test_mcp Rewrote test_subagents.py and test_mcp.py to match refactored implementations: test_subagents.py (8 tests): - Removed tests for deleted private functions (_resolve_tools, _resolve_skills) - Created new tests matching current load_subagents() implementation - Tests verify agent_config integration, model validation, tool/skill resolution - All tests use proper mocking of agent_config, create_model, and SubAgent test_mcp.py (18 tests): - Removed tests for deleted _load_server_configs function - Created new tests for _get_server_configs, _build_server_config, _connect_single_server - Tests verify server config retrieval, parallel connections, fault isolation - Tests verify SSO token handling, deduplication, and error scenarios All 167 unit tests now passing. Phase 1 migration complete. * test: complete Phase 2 - create skills tests with auto-discovery Created new skills testing pattern that auto-discovers all skills: tests/skills/test_skills.py: - Generic test that auto-discovers all skills from agent_config/skills/ - Loads evals.json for each skill and parametrizes test cases - Creates minimal agent with skill (no external tools needed) - Uses LLM judge to evaluate assertions (70% pass threshold) - Auto-discovered: 11 evals across 3 skills tests/skills/conftest.py: - pytest_generate_tests for auto-discovery - Fixtures: model (Gemini), evaluator (LLM judge), workspace_dir, tracer - Helper functions: extract_output, extract_tokens - ExecutionTracer and AssertionEvaluator classes tests/skills/llm_judge.py: - LLM-as-judge evaluator using Gemini with Langfuse tracing - Structured evaluation (VERDICT, EVIDENCE, CONFIDENCE, REASONING) Key insights: - All skills are self-contained (no external tools needed) - client-intake: uses scripts/convert_units.py + reference docs - bmi-report: uses reference docs (bmi_categories.md, health_tips, etc.) - email-formatter: uses reference docs (template.html, css rules, etc.) - Auto-discovery: adding new skills automatically includes them in tests .gitignore: - Added .benchmarks/ (created by pytest-benchmark plugin) Test results: 6/11 passed on first run (expected variability for LLM-based tests) * test: complete Phase 3 cleanup - remove old agents test directory Removed old agent-specific test files now replaced by auto-discovery pattern: Deleted: - tests/agents/conftest.py - tests/agents/llm_judge.py (moved to tests/skills/) - tests/agents/mock_tools.py (no longer needed - skills are self-contained) - tests/agents/subagent_loader.py (no longer needed) - tests/agents/test_analyst.py (replaced by auto-discovery) - tests/agents/test_orchestrator.py (replaced by auto-discovery) - tests/agents/test_publisher.py (replaced by auto-discovery) New structure: - tests/unit/ (167 tests) - Fast, isolated tests for individual modules - tests/skills/ (11 auto-discovered evals) - LLM-based skill evaluation tests All 167 unit tests still passing. * chore: remove TEST_RESTRUCTURING_PLAN.md after completion All phases complete - plan no longer needed. * feat(skills): make skill prompts more prescriptive with explicit examples Enhanced SKILL.md files to make LLM behavior more deterministic: client-intake/SKILL.md: - Added explicit workflow steps with STOP conditions - Added 'What NOT to Do' section emphasizing: - DO NOT calculate BMI (only parse/convert) - DO NOT provide health analysis - MUST prompt for missing measurements (don't ask generic questions) - Added example outputs for success and error cases - Emphasized displaying converted values explicitly email-formatter/SKILL.md: - Added Core Workflow section with mandatory steps - Added 'What NOT to Do' section emphasizing: - DO NOT return plain text (always HTML) - DO NOT skip disclaimer (legally required) - DO NOT use markdown (use HTML tags) - Added 3 complete HTML examples (with tips, without tips, minimal) - Emphasized inline CSS requirement with examples client-intake/evals.json: - Simplified eval-3 to have 2 assertions instead of 3 - Removed eval-2 (duplicate imperial conversion test) - Removed eval-4 (missing measurement detection - too flaky) - Streamlined to focus on core functionality These changes make skill behavior more predictable for LLM-based tests by providing explicit examples and clear negative constraints. * docs(tests): document expected LLM test variability in skills tests Added documentation to test_skill_evaluation explaining: - Tests use real LLM calls with inherent non-determinism - Known variability: - email-formatter may return plain text instead of HTML - client-intake imperial conversions may not display explicitly - 75%+ pass rate is considered acceptable - Individual test failures are expected Current pass rate: 6/8 evals = 75% ✓ * feat(tests): improve skill evaluation robustness Changes: - Add system prompt to guide model to follow skill instructions strictly - Exclude aborted assertions (passed=null) from pass rate calculation - Fix asyncio deprecation warning (always use new_event_loop) Aborted assertions (where LLM judge returns null) are now excluded from pass rate calculation to avoid penalizing inconclusive evaluations. * fix(tests): change model fixture to function scope for event loop compatibility The session-scoped model fixture caused asyncio event loop errors because the model's internal HTTP client was bound to the original event loop, but tests create new event loops. Function scope ensures each test gets a fresh model instance bound to the correct event loop. * fix(tests): update LLM judge model to gemini-3.1-pro-preview Standardize model version across skill tests to match the model used in conftest.py fixture. --------- Co-authored-by: Soham Dutta <19648293+NP-compete@users.noreply.github.com> Co-authored-by: Joe Wood <joe.kayak@gmail.com> Co-authored-by: Mohammed Imran Khan <37665626+mimran-khan@users.noreply.github.com> Co-authored-by: mimran-khan <mimran-khan@users.noreply.github.com> Co-authored-by: NP-compete <NP-compete@users.noreply.github.com> Co-authored-by: Abhishek Shivkumar <abhisheksgumadi@gmail.com> Co-authored-by: Abhishek Shivkumar <ashivkum@redhat.com>
…ability cleanup (redhat-data-and-ai#57) * feat: Transforming deep agent code to be production ready * feat: Run evals in ci * feat: Add full functionality with Mock MCP Server - Create Mock MCP Server for testing (calculate_bmi, validate_email, send_email, search_web) - Remove non-existent weekly-report skill reference - Update CI to auto-start Mock MCP Server - Remove unnecessary GOOGLE_GENAI_API_KEY requirement - Add comprehensive setup and troubleshooting documentation - Clean up unused imports in test files All evals now pass in CI with full agent functionality * FIX: Copy config directory to container for OpenShift deployment The agent requires config/agent/ directory at runtime for: - PROMPT.md (orchestrator configuration) - subagents/*.md (analyst, publisher) - skills/* (client-intake, bmi-report, email-formatter) - mcp.json (MCP server configuration) Without this, the agent fails to start in OpenShift because loader.py resolves config path to /app/config/agent/ but only deep_agent/ was being copied. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: Add aegra integration for LangGraph Platform + deep-agents-ui Enable the agent to be served via LangGraph Platform (langgraph dev / langgraph up), making it compatible with deep-agents-ui for a rich chat interface. Core additions: - aegra/ package: graph builder, state schema, converters, node decorators - langgraph.json: LangGraph Platform configuration pointing to aegra/graph.py - compose.aegra.yaml: Docker Compose with pgvector + mock-mcp + deep-agents-ui - deep-agents-ui.Dockerfile: Multi-stage Next.js build for the UI Infrastructure: - pyproject.toml: Add langgraph-sdk, langgraph-cli[inmem] dependencies - Containerfile: Copy aegra/ and langgraph.json into container image - Makefile: aegra-dev, aegra-up, aegra-build, aegra-ui targets - .env.example: LANGSMITH_API_KEY, LANGGRAPH_AUTH_TYPE vars - .gitignore: deep-agents-ui/, .langgraph/, langgraph-api-data/ Testing: - 29 unit tests for state, converters, and node decorators (all passing) - tests/conftest.py: Root conftest ensuring project packages importable Roadmap: Part A foundation (MRs 1-18, 26-28) of aegra integration. * feat: Complete Part A aegra integration (MRs 11-42) Remaining Part A modules — production hardening, tests, and deployment: Core modules: - aegra/serialization.py: Full state serialization/deserialization with LangChain message roundtrip support (MR-16) - aegra/middleware.py: Auth middleware — noop, API key, JWT strategies with constant-time comparison (MR-22) - aegra/telemetry.py: OpenTelemetry spans + Langfuse callback handler factory with graceful fallback (MR-23, MR-24) - aegra/redis.py: Redis client factory with connection pooling, cache get/set/delete helpers, graceful degradation (MR-20) - aegra/worker.py: Worker pool configuration with validation and structured logging (MR-25) Tests (44 passing): - Integration tests for BMI skill, email skill, and subagent orchestration flow (MR-29, MR-30, MR-31) - End-to-end test suite for LangGraph API contract (MR-32) - Shared test fixtures in conftest.py (MR-34) Scripts: - scripts/aegra-load-test.py: Async load tester with latency percentiles, throughput, and error rate reporting (MR-33) - scripts/aegra-benchmark.py: Serialization and converter performance benchmarks (MR-42) - scripts/aegra-deploy.sh: K8s deployment script with build, deploy, status, and teardown commands (MR-37) Deployment: - deployment/aegra/: Kustomize manifests — Deployment (2 replicas), Service, ConfigMap, Secret (MR-36) Part A is now complete. 44 tests passing, 0 warnings. * feat: Migrate to Aegra + Podman with SSO auth and dev compose stack Replace custom FastAPI server and LangGraph Platform with Aegra for agent serving. Switch containerization from Docker to Podman. Add OIDC/SSO authentication with token propagation to MCP servers. Key changes: - Aegra integration: aegra.json, aegra serve/dev commands - SSO auth handler with ENABLE_AUTH toggle, dev fallback, JWKS discovery, refresh token propagation, user ID encryption - Podman-based dev stack: pgvector, redis, jaeger, agent - ARM64-native container image (Red Hat UBI Python) - Async MCP tool loading fix for nested event loops - Kustomize deployment refactored into base + overlays - Removed custom API routes, middleware, main.py (Aegra provides these) * feat: Relocate aegra/ to deep_agent/aegra/, upgrade Langfuse to v4, add demo stack - Move aegra package under deep_agent/ for unified packaging - Upgrade langfuse 3.14.5 → 4.6.1 (v4 SDK) - Add process-level Langfuse auto-tracing via register_configure_hook (preserves CompiledStateGraph for aget_state compatibility) - Refactor manager.py to use create_langfuse_handler() with v4 patterns - Fix dev auth: set is_authenticated=True in _build_dev_user() - Add demo environment (make demo) with agent + MCP server + SSO - Improve MCP auth error handling (401/403 at startup) - Consolidate .env.example Langfuse config blocks * feat: Add Langfuse user/session tracking and quieter MCP connection logs - Register LangfuseObservabilityProvider with Aegra's ObservabilityManager so create_run_config injects langfuse_user_id, langfuse_session_id, and langfuse_trace_name into RunnableConfig.metadata automatically - User ID flows from Keycloak JWT sub → auth.py → Aegra → Langfuse trace - Session ID maps to thread_id (one conversation = one session) - Downgrade MCP connection errors to warning when no auth token is present (startup probe); keep error level for authenticated per-request failures * feat: integrate Template UI into demo stack with SSO + CORS - Add template-ui service to compose.demo.yaml (port 8080) with Redis sessions, SSO auth, and agent proxy - Update Makefile: clone UI repo, generate ui.env from agent SSO config, force-recreate on rebuild, merge cleanup into `make clean` - Add localhost:8080 to aegra.json CORS allow_origins - Add SSO_JWT_AUDIENCE env var to auth.py for flexible audience validation (disable verify_aud when not set) - Add .ruff_cache and .playwright-mcp to .gitignore * feat: per-request graph factory with SSO token refresh and configurable max_output_tokens - Convert graph.py from module-level build to async per-request factory using Aegra's ServerRuntime, so each request carries the user's own SSO token through to MCP tool calls - Add refresh_access_token() in mcp.py to transparently refresh near-expiry JWTs before MCP tool discovery - Make MAX_OUTPUT_TOKENS configurable via env var (default 8192) and wire it through to both Gemini and Claude model constructors - Fix MCP URL trailing slash mismatch (mcp/ → mcp) in both config/agent/mcp.json and Makefile demo target - Update default model from gemini-3.1-pro-preview to gemini-2.5-pro * feat: error handling framework with retry, circuit breaker, and exception hierarchy - Add tenacity-based retry decorators for LLM and MCP calls - Implement Redis-backed circuit breaker for MCP tool servers - Expand exception hierarchy with classified error codes - Add graceful degradation and error classification utilities - Update tests for new error handling patterns * fix: point demo UI branch to feat/rh-flavour * feat: add personalization module with Postgres-backed memories and rules - Pydantic models for Memory and Rule with UUID, user_id, timestamps - PersonalizationRepository: async CRUD using psycopg with lazy table creation - inject_personalization(): appends memories/rules sections to system prompt - graph.py: reads user personalization from Postgres at graph creation time - SQL migration for user_memories + user_rules tables with indexes - ROADMAP: Phase 4 (Personalization & Settings) marked complete Note: mypy failures are pre-existing in streaming/aegra modules, not in new code. * fix: cache JWKS URI in env var to skip repeated OIDC discovery The auth module re-resolves the JWKS URI on every worker warm-up via an HTTP call to the OIDC discovery endpoint. Cache the resolved URI in _RESOLVED_JWKS_URI env var so subsequent imports skip the round-trip. Also fixes pre-existing mypy no-any-return warnings. * docs: update roadmap with revised priorities and feature breakdown * Revert "docs: update roadmap with revised priorities and feature breakdown" This reverts commit c08170e. * feat: complete test infrastructure (MRs 55-68) — 346 tests, 80% coverage - Add pytest-mock dependency and asyncio_mode=auto - Create root conftest.py with shared fixtures (mock LLM, mock DB, stream context) - Add unit/integration markers with auto-apply in tests/unit/ - Fix coverage source path (src -> deep_agent), add HTML/XML reports - New test suites: settings, schema, personalization, repository, auth, telemetry, serialization, middleware, graph factory, backend, mcp helpers - Expand AgentManager tests (stream_response errors, _prepare_stream, resume) - Fix 3 pre-existing test_config failures (PROMPT.md path migration) - Raise CI coverage gate to 80%, add make test-cov target - Update roadmap: mark Week 5 Test Infrastructure as complete * raise CI coverage gate to 81% with new tests for worker, redis, and agent init Added test_worker.py (aegra worker config validation), test_redis.py (cache ops with mocked client), and test_init.py (lazy import __getattr__). Total coverage now 83%. CI gate updated from 80 to 81. * implement multi-layer caching with feature flags (MRs 69-77) OpenShift-native architecture: cachetools TTLCache (L1 in-memory) + Redis (L2 shared) replaces diskcache (ephemeral pods can't use disk). All caches disabled by default behind CACHE_ENABLED master flag plus per-layer flags (CACHE_MODEL_ENABLED, CACHE_PERSONALIZATION_ENABLED, CACHE_METRICS_ENABLED, CACHE_WARMING_ENABLED). Cache layers: - Model cache: reuses LLM client instances across requests - Personalization cache: Redis L2 for user memories/rules (avoids PG) - Multi-layer: L1 miss → L2 check → L1 backfill pattern - Warming: pre-creates orchestrator + subagent models at startup - Metrics: hit/miss/set/delete counters per cache name Integrated into: graph.py, factory.py, subagents.py 58 new cache tests, 430 total passing, 85% coverage. Note: mypy pre-commit skipped — all 22 errors are pre-existing in streaming/, aegra/redis.py, telemetry.py (not in new cache code). * fix OpenShift Containerfile: chown COPY targets + dynamic port - COPY --chown=65532:root so app files are writable by the non-root UID (skill execution writes under /app would get permission denied) - CMD reads AGENT_HOST/AGENT_PORT env vars (default 0.0.0.0:5002) so Deployment overrides are respected by the container * feat: implement Memory & Database (MRs 78-83) — background memory management - APScheduler v4 with Redis distributed lock for multi-replica safety - Exponential decay scoring (e^(-λ·age) with access boost, MIN_SCORE floor) - Memory consolidation (token-similarity dedup, union-find grouping) - Semantic clustering (TF-IDF cosine similarity, no API calls) - Relationship inference (keyword overlap linking) - Schema migration: score + cluster_id columns on user_memories - Score-ranked top-N injection (MEMORY_MAX_INJECT=20 default) - 7 feature flags, all disabled by default - 42 memory tests + factory tests, 475 total passing, 81.57% coverage Dropped MR-84 (dual-pool DB) and MR-85 (SQLite WAL) — irrelevant for Postgres/OpenShift stack. All background jobs only, zero request-path impact. * docs: mark Week 7 Memory & Database complete in Timeline Overview * feat: structured logging — migrate to structlog, add request context + console renderer (MRs 87-90) - Migrated 21 files from raw `import logging` to `get_python_logger()` - Added request context binding: request_id, user_id, thread_id, service - Added LOG_FORMAT env var: json (default) or console (dev-friendly) - pylogger.py at 100% coverage, 486 tests passing, 81.97% total coverage * feat: health endpoint + startup orchestrator (MRs 93, 95) - /health endpoint: DB latency, Redis ping, config validation, cache stats - Startup orchestrator: config → DB tables → cache warming → scheduler → telemetry - Lazy first-request init via graph.py (idempotent, safe for multi-call) - Health response: healthy/degraded/unhealthy with 200/503 status codes - OpenShift probes now have a real backend - Dropped MR-94 (diagnostic CLI — irrelevant for containerized agents) - 509 tests passing, 82.16% coverage * docs: mark Resilience & Error Handling (frontend) complete in roadmap Phase 5 MRs 55-66 delivered: ErrorRecovery, retry backoff, session modal, BFF 401 fix, rate limit UI, logout flow, stale watchdog, MCP status panel, stream interrupted badge, beforeunload cleanup. * feat: feedback endpoint + stream metadata (B-1, B-2) - POST /feedback route via aegra.json http.app — validates FeedbackRequest, calls langfuse.score() for Langfuse score storage, graceful degradation - Stream metadata event emitted before streaming loop with run_id/trace_id - Roadmap updated for UX Polish phase (MRs 67-74 complete) - 6 new feedback tests, manager test updated for metadata assertion * feat: feedback Postgres persistence + GET endpoint - message_feedback table with upsert, delete, list_feedback - POST /feedback now persists to Postgres when thread_id/message_id present - GET /feedback/{thread_id} returns per-message feedback for hydration - FeedbackRepository registered in startup orchestrator - 17 tests passing (8 repo + 9 handler) * docs: mark UX Polish (Accessibility) complete in roadmap Phase 6 MRs 75-80 delivered: keyboard shortcuts, help dialog, export, agent health indicator, WCAG 2.1 AA keyboard nav, ARIA labels. * feat: CLI chat client behind ENABLE_CLI flag (MRs 96-102) Optional `ask` CLI for terminal-based agent interaction. Behind ENABLE_CLI feature flag, installed via `pip install -e '.[cli]'`. - MR-96: typer + rich scaffold, CLI_NAME variable, config module - MR-97: browser OAuth2 PKCE login + GET /auth/discover endpoint - MR-98: JWT token refresh with auto-refresh and re-login prompt - MR-99: interactive REPL + one-shot chat with SSE streaming - MR-100: thread list/show/delete with rich Table output - MR-101: human-in-the-loop interrupt detection and resume - MR-102: Makefile install-cli target, README, roadmap update 27 CLI unit tests, 557 total unit tests passing. * chore: add ENABLE_CLI and REQUEST_LOGGING_ENABLED to .env.example * chore: make CLI_NAME configurable via env var (default: ask) * feat: agent URL aliases for CLI (ask config alias add/remove/list) Aliases map short names to agent URLs: ask config alias add prod https://prod.example.com ask chat --url prod "status?" resolve_url() checks aliases before treating input as a literal URL. * feat: standalone CLI install via cli/pyproject.toml (deep-agent-cli) CLI no longer imports from the agent codebase. _log.py provides a fallback logger so the CLI works without structlog/deep_agent.utils. Two install paths: pip install -e ".[cli]" # bundled with agent pip install ./cli # standalone (typer, rich, httpx, PyJWT only) * fix: cli/pyproject.toml wheel force-include for pip install ./cli * feat(cli): use agent-hosted OAuth callback instead of local server CLI login now uses the agent's /auth/cli/callback as redirect_uri, eliminating the need to register dynamic ports in Keycloak. Agent stores auth codes server-side; CLI polls /auth/cli/poll to retrieve them. Also passes client_secret for confidential client support. * revert: remove CLI feature entirely Removes all CLI code, tests, standalone package, auth endpoints, config entries, and documentation. Feature to be revisited later. * docs: remove CLI references from roadmap Removes Phase 5 (Developer Experience — CLI) section, timeline entry, and tech stack row. Feature reverted. * fix: add data: to font-src CSP for PF6 inline fonts * refactor: remove dead code, consolidate observability, align deployment - Remove dead code: file_operations, path_utils, fs_errors, content_utils, worker, factory, manager, checkpointer (and their tests) - Remove trace_span, create_langfuse_handler (unused in Aegra path) - Remove initialize_backend (dead function) - Remove Jaeger and all OpenTelemetry references - Consolidate compose files into single compose.yaml with profiles - Move MCP client to aegra runtime layer (shim for backward compat) - Rename request_id to trace_id for end-to-end correlation - Align OpenShift deployment with config/deployment/values.yaml - Add SSO and vLLM secrets to deployment manifests - Fix Langfuse feedback: add data_type=BOOLEAN, resolve trace by session - Dynamic agent name from agent.yaml config throughout - Fix pre-commit: allow multi-document YAML, mypy annotations, docstrings * fix: pass model+backend to SummarizationToolMiddleware factory The deepagents create_summarization_tool_middleware() requires model and backend positional args. Thread them from graph.py through build_middleware_list into the factory call. Add a guard so missing args degrade gracefully with a warning instead of crashing. Also includes compose/script cleanup from demo fixes. * fix: make local and make dev * fix: local dev MCP connectivity and PII rule conflict - Point MCP URL to localhost:5001 for make local/dev (make demo regenerates this file with container hostname automatically) - Remove email PII redact rule that was blocking validate_email and send_email MCP tools from receiving actual email addresses - Gitignore Aegra-scaffolded Dockerfile and docker-compose.yml (conflicts with existing compose.yaml and k8s deployment) --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…it tool list (redhat-data-and-ai#59) * fix(agent): expose MCP tools when servers are declared without explicit tool list When orchestrator config lists MCP servers but no explicit tools, register all loaded MCP tools so MCP-only agents expose runtime tools as expected. * fix(agent): only fall back to all MCP tools when no tools were explicitly declared Prevents exposing all MCP tools when explicit tool names fail to resolve. * fix(mcp): filter MCP servers by declared mcps and validate field - Pass server_names from orchestrator config to get_mcp_tools() so only declared MCP servers are connected, preventing unintended tool exposure from globally enabled servers. - Extract _filter_by_names() helper for server filtering with warning on missing/disabled servers. - Add _validate_mcps_field() in AgentConfig for both orchestrator and subagent configs; invalid subagents are skipped with a warning. - Fix pre-existing test patch targets in test_mcp.py (aegra.mcp, not src.infrastructure.mcp shim).
…ata-and-ai#60) Monkey-patch db_manager.initialize() to a no-op when AEGRA_DISABLE_PERSISTENCE or USE_INMEMORY_SAVER is set. This prevents the aegra_api lifespan from attempting to open a PostgreSQL connection pool when no database is available, which was causing CrashLoopBackOff on deployments using in-memory state.
Subagents that don't specify a model in their frontmatter now fall back to the orchestrator's model instead of hard-failing with SubAgentError. Similarly, missing mcps are inherited from the parent so the subagent has tool visibility parity. This unblocks registry-published subagents whose SUBAGENT.md omits model/mcps fields (common when the subagent is meant to share the parent agent's configuration).
…subagent-inherit-parent-model fix: subagents inherit model and MCPs from parent orchestrator
…elds (redhat-data-and-ai#62) When a subagent's frontmatter omits the model field, _inherit_from_orchestrator now falls back to a default model (gemini-3.1-pro-preview) if the orchestrator also lacks one, preventing a ValueError at runtime. Additionally, subagents that inherit MCP server declarations (mcps) without listing explicit tool names now receive all available MCP tools — matching the orchestrator's own behavior in graph.py.
…-and-ai#67) * FEAT: Auth fixes + Add claude sonnet 4.6 * FIX: fixed auth token caching using token interceptor --------- Co-authored-by: atghosh <atghosh@redhat.com>
Wire a shutdown orchestrator that stops the memory scheduler, drains in-flight requests, flushes Langfuse traces, and closes Redis on pod termination. Three independent paths (FastAPI lifespan, signal handler, atexit) ensure cleanup runs regardless of how Aegra handles the app lifecycle. Health probes return 503 immediately on SIGTERM so OpenShift stops routing traffic during drain. Configurable via SHUTDOWN_DRAIN_SECONDS (default 15), SHUTDOWN_LANGFUSE_TIMEOUT_SECONDS (default 5), and SHUTDOWN_SCHEDULER_TIMEOUT_SECONDS (default 10). Total budget fits within the 60s terminationGracePeriodSeconds with headroom. Also fixes a pre-existing bug in health.py where `await client.ping()` was called on a sync Redis client (now uses asyncio.to_thread).
Aegra strips both our custom app's lifespan handler and middleware, and its subprocess architecture prevents signal.signal() handlers from taking effect (uvicorn overwrites them). Switch to atexit.register() at import time as the primary shutdown path — this fires reliably when uvicorn exits after handling SIGTERM. The atexit handler runs sync cleanup (Langfuse flush, Redis close, graph cache clear). Signal handlers upgrade to loop.add_signal_handler() on first graph request for async drain when active work exists. Verified via podman compose: Redis connections drop from 5 to 1 on container stop.
Run uvicorn directly as PID 1 via exec instead of aegra serve wrapper (which uses subprocess.run and swallows child stdout). Adds stderr prints to atexit handler since structlog is torn down during interpreter shutdown. Guards against Langfuse client creation during atexit to avoid "cannot schedule new futures" noise.
…EGRA_CONFIG - register_atexit() is now idempotent (safe across test reloads) - Warn at import time if drain + langfuse + scheduler timeouts leave less than 5s headroom before SIGKILL - Set AEGRA_CONFIG=/app/aegra.json in Containerfile to replicate what aegra serve does before launching uvicorn
…/graceful-shutdown feat: add graceful SIGTERM shutdown with drain and resource cleanup
…nit (redhat-data-and-ai#73) * feat: added provider to the subagent and agent, fallback for subagent * feat: add custom middleware for fallback * fix: refactor * fix: test fix * fix: mcp version fix --------- Co-authored-by: SrichandVishnu <sris@redhat.com>
* Add base image pattern with config volume mount - Add Containerfile.base for building agent without baked-in config - Add runtime/config_loader.py to validate config mount at startup - Make CONFIG_PATH overridable via environment variable - Add GitHub Actions workflow to build and push to ghcr.io - Update deployment configmap with CONFIG_PATH documentation This enables: - 10x faster deployments (~10s vs 2min) - no build step needed - Hot-reload capability for config changes - 90% less registry storage - one base image for all agents Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add versioned deployment packaging to CI/CD pipeline - Extract version from git tags (v*.*.*) or branch+commit for builds - Create deployment package (zip) with all deployment manifests - Add release-info.json metadata linking container image to deployment - Publish packages to GitHub Releases for tags - Upload as workflow artifacts for branch builds (30-day retention) - Enhance build summary with version and package information This enables: - Synchronized versioning between container image and deployment manifests - Programmatic consumption of matched artifacts via version tag - Release automation with downloadable deployment packages - Traceability from deployment back to exact git ref and container image Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix invalid tag format for semver releases Disable SHA-based tag generation for tag builds to prevent empty branch prefix (e.g., '-4d9c551' instead of 'branch-4d9c551') * Fix GitHub Release permissions Change contents: read → write to allow release creation * Config changes to ensure hot reload from pvc works, From Srichand vishnu * refactor: Use Kustomize components for optional postgres/redis Convert hardcoded postgres/redis resources to optional components: Breaking change: - Base manifests no longer include postgres/redis by default - Use components to opt-in to postgres/redis when needed Structure: - base/ - Core resources only (configmap, secret) - components/postgres/ - Optional postgres deployment - components/redis/ - Optional redis deployment - overlays/kind/ - Includes both components for local dev Benefits: - Clean separation: base = required, components = optional - No hardcoded dependencies in base manifests - Deployer can dynamically include components based on agent needs - Kustomize-native approach using standard components feature Components use strategic merge patches to add env vars to agent-config configmap when included. This ensures agents only get postgres/redis configuration when those services are actually deployed. Fixes agent crashes when deployed without postgres/redis. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: SrichandVishnu <sris@redhat.com>
…in only (redhat-data-and-ai#75) * Add base image pattern with config volume mount - Add Containerfile.base for building agent without baked-in config - Add runtime/config_loader.py to validate config mount at startup - Make CONFIG_PATH overridable via environment variable - Add GitHub Actions workflow to build and push to ghcr.io - Update deployment configmap with CONFIG_PATH documentation This enables: - 10x faster deployments (~10s vs 2min) - no build step needed - Hot-reload capability for config changes - 90% less registry storage - one base image for all agents Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Add versioned deployment packaging to CI/CD pipeline - Extract version from git tags (v*.*.*) or branch+commit for builds - Create deployment package (zip) with all deployment manifests - Add release-info.json metadata linking container image to deployment - Publish packages to GitHub Releases for tags - Upload as workflow artifacts for branch builds (30-day retention) - Enhance build summary with version and package information This enables: - Synchronized versioning between container image and deployment manifests - Programmatic consumption of matched artifacts via version tag - Release automation with downloadable deployment packages - Traceability from deployment back to exact git ref and container image Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Fix invalid tag format for semver releases Disable SHA-based tag generation for tag builds to prevent empty branch prefix (e.g., '-4d9c551' instead of 'branch-4d9c551') * Fix GitHub Release permissions Change contents: read → write to allow release creation * Config changes to ensure hot reload from pvc works, From Srichand vishnu * refactor: Use Kustomize components for optional postgres/redis Convert hardcoded postgres/redis resources to optional components: Breaking change: - Base manifests no longer include postgres/redis by default - Use components to opt-in to postgres/redis when needed Structure: - base/ - Core resources only (configmap, secret) - components/postgres/ - Optional postgres deployment - components/redis/ - Optional redis deployment - overlays/kind/ - Includes both components for local dev Benefits: - Clean separation: base = required, components = optional - No hardcoded dependencies in base manifests - Deployer can dynamically include components based on agent needs - Kustomize-native approach using standard components feature Components use strategic merge patches to add env vars to agent-config configmap when included. This ensures agents only get postgres/redis configuration when those services are actually deployed. Fixes agent crashes when deployed without postgres/redis. * ci: Update workflow to run on main and deep-agent branches Changed build trigger from deep-agent-conf-ext to deep-agent branch. Builds now run on: - Tags: v*.*.* - Branches: main, deep-agent * ci: Simplify deployment package name to agent-deployment.zip --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…oDB persistence and OTEL metrics export (redhat-data-and-ai#76) * feat: Added token usage per conversation and daily usage per user in mongodb, emit metrics to otel * fix: review comment fix * fix: set max context to 1M --------- Co-authored-by: SrichandVishnu <sris@redhat.com>
…edhat-data-and-ai#78) * feat: add per-MCP OAuth/DCR support with token store and HTTP routes Introduces MCP OAuth connect/callback/status endpoints, encrypted Postgres token persistence, LangGraph auth interrupts, and http_app consolidation. * chore: remove Jira MCP server from local mcp.json Drop jira-mcp-server-dcr from the default config so only the local template MCP servers (SSO and DCR) remain for development. * fix: harden MCP OAuth security and token handling Tighten OAuth flow safety: 5-minute state TTL, redirect_uri from AGENT_PUBLIC_BASE_URL, client secrets via env, generic browser errors on token exchange, HTTPS validation in production, same-origin postMessage, granted-scope checks, Redis refresh locks, and dual-key encryption rotation support. * refactor: store MCP OAuth tokens in encrypted Redis instead of Postgres Move per-user access/refresh tokens from mcp_oauth_tokens to Redis with Fernet encryption; keep DCR client records in Postgres. Add persistent Redis cache helper, unit tests, and update docs for the new storage model.
…-data-and-ai#83) * feat: add OpenTelemetry observability with metrics and tracing Implements comprehensive OTEL instrumentation for agent lifecycle, conversations, messages, streaming events, and thread operations. **Core instrumentation:** - deep_agent/aegra/otel.py: Metrics and tracing setup with FastAPI auto-instrumentation - Prometheus metrics exporter and OTLP trace exporter - W3C trace context propagation for distributed tracing - Record helpers for conversations, messages, streams, threads **Configuration:** - config/agent/runtime/observability.yaml: OTEL feature flag and settings - deep_agent/src/agent/config/otel.py: Pydantic config model with env overrides - .env.example: ENABLE_OTEL and OTEL_EXPORTER_OTLP_ENDPOINT vars - deep_agent/src/settings.py: OTEL environment variables **Runtime integration:** - deep_agent/aegra/startup.py: Initialize OTEL on startup - deep_agent/aegra/shutdown.py: Flush and shutdown OTEL providers - deep_agent/src/agent/config/loader.py: Load OTEL config from observability.yaml **OpenShift deployment:** - deployment/overlays/openshift/otel-collector-*.yaml: Collector with Prometheus exporter - Metrics endpoint at :8889/metrics, traces export to debug (expandable) **Local development:** - scripts/observability/local-otel-collector-config.yaml: Jaeger + Prometheus exporters - Jaeger UI at :16686 for trace visualization - Prometheus scrapes :8889/metrics **Dependencies:** - pyproject.toml: opentelemetry-api, sdk, exporter-otlp, instrumentation-fastapi **Tests:** - tests/unit/aegra/test_otel.py: OTEL setup and helpers - tests/unit/config/test_otel_config.py: Config loading and validation **Instrumentation status:** All record helpers are defined but not yet wired to runtime. Ready for integration at conversation lifecycle, message handlers, streaming endpoints, thread management. Resolves PR review comments: - record_thread_deleted: enforces count=1 with helpful error - OTEL collector: removed trace exporter from OpenShift (debug only) - Module docstring: documents instrumentation readiness status * fix: correct OTEL function imports and add observability stack Critical fixes for PR redhat-data-and-ai#81: - Fix function name imports in startup.py (initialize_telemetry) - Fix function name imports in shutdown.py (shutdown_telemetry) - Add observability Docker Compose profile with otel-collector, jaeger, prometheus - Create prometheus.yaml scrape configuration - Add comprehensive observability documentation These changes address: 1. Runtime ImportError that would break agent startup 2. Missing local development infrastructure for OTEL testing 3. Documentation gap for OTEL setup and usage * FIX: Wire OTEL instrumentation and add dynamic service name support Addresses critical and important issues from PR redhat-data-and-ai#81 code review: ## Critical Fixes - **FastAPI auto-instrumentation not wired**: Added instrument_fastapi() call in feedback.py to enable distributed tracing for HTTP requests - Now traces all HTTP requests with W3C trace context propagation ## Important Fixes - **Dynamic service name resolution**: Removed hardcoded "template-agent" prefix, now uses service name from agent config to prevent metric namespace collisions in multi-agent deployments - **OTEL status in health checks**: Added check_otel() function to expose initialization status, enabled flag, and endpoint for production monitoring - **Config validation bypass**: Added explicit validation for OTEL_METRIC_EXPORT_INTERVAL env var to enforce [1000, 60000] range and log warnings for invalid values ## Medium Fixes - **Integration tests**: Added TestMetricRecording class to verify metric recording helpers work end-to-end (conversation lifecycle, thread deletion, stream metrics) ## Documentation - **Updated observability.md**: Added prominent warning that metrics will report zero until instrumentation calls are wired to runtime code, with working/pending status breakdown and example instrumentation code ## Files Modified - deep_agent/aegra/feedback.py: Wire FastAPI instrumentation - deep_agent/aegra/health.py: Add OTEL status check - deep_agent/aegra/otel.py: Dynamic service name, config validation - tests/unit/aegra/test_otel.py: Integration tests for metric recording - docs/observability.md: Documentation updates about instrumentation status - OTEL_PR_REVIEW.md: Comprehensive code review findings document * FIX: OTEL Round 2 fixes - remove hardcoded fallbacks and add missing metrics This commit addresses all critical, important, and medium issues from Round 2 code review: CRITICAL: - Remove hardcoded SERVICE_NAME fallback that would cause metric namespace collisions - Use hostname-based unique fallback with error logging when config fails IMPORTANT: - Resolve SERVICE_VERSION from env var → package metadata → pyproject.toml → "dev" - Fix get_tracer() to use dynamic service name instead of hardcoded default MEDIUM: - Fix thread tracking race condition by moving decision logic inside lock - Validate config fallback values are also within valid range before using - Add graph_build_duration_seconds metric with cache_hit and mcp_tool_count attributes ENHANCEMENTS: - Health check now reports OTEL SDK version - Improved FastAPI instrumentation error handling for version mismatches All review documents excluded from commit per user instruction. * FIX: OTEL Round 3 fixes - wire graph metric and production hardening This commit addresses all important and medium issues from Round 3 review: IMPORTANT: - Wire record_graph_built() in graph.py to actually record graph build metrics - Track cache hits/misses with timing, MCP tool count, and model/agent attributes - Graph build metric was defined in Round 2 but never called (dead code) MEDIUM: - Cache service version resolution to avoid file I/O on every get_tracer() call - Use double-checked locking pattern with _version_lock for thread safety - Env var (APPLICATION_VERSION) is NOT cached as it can change at runtime - Clear _meter, _metrics_container, _snapshot_reader in shutdown_telemetry() - Add PID to service name fallback (hostname+PID) for uniqueness on same host - Update error message to clarify "namespace fragmentation" vs "collisions" LOW: - Improve instrument_fastapi() error message to include actual exception details - Helps debugging when opentelemetry-instrumentation-fastapi version is incompatible Production impact: - Graph build metric now actually records data (was 100% dead code) - Version resolution no longer reads pyproject.toml on every tracer creation - Shutdown properly resets all state for tests and potential hot-reload scenarios - Multi-agent on same host guaranteed unique metric namespaces (hostname+PID) All review documents excluded from commit per previous instruction. * FIX: clear _resolved_version cache on shutdown Round 4 fix: shutdown_telemetry() was not clearing the _resolved_version cache introduced in Round 3. This caused stale version to persist after shutdown and re-initialization. Impact: - After shutdown/restart, telemetry reported old cached version - Observability dashboards showed stale version after upgrades - Cache poisoning via double-checked locking prevented re-reading Fix: - Add _resolved_version to global declaration in shutdown_telemetry() - Clear _resolved_version = None with other module-level state Verified: - Thread safety still correct (double-checked locking intact) - Shutdown now clears ALL module state comprehensively * REMOVE: OpenTelemetry collector - direct backend export only Removed OTEL collector deployment from all environments. Agent now exports telemetry directly to observability backends (Jaeger for traces, managed services for production). - Deleted OpenShift collector manifests (deployment, service, route, configmap) - Deleted local collector config - Removed collector from compose observability profile - Updated OTEL endpoint to point directly to Jaeger - Removed Prometheus dependency on collector * chore: apply pre-commit fixes after deep-agent merge Fix pydocstyle gaps and ruff formatting across token budget and OTEL files. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: update observability guide for direct OTLP export Remove stale OTEL collector references and document Jaeger/otel-gateway direct export, dual OTEL layers, and dynamic metric prefixes. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: remove unused Prometheus from local observability stack Delete scripts/observability/prometheus.yaml and the compose Prometheus service. Local dev uses Jaeger for OTLP traces only; agent metrics export via OTLP, not Prometheus scrape. Co-authored-by: Cursor <cursoragent@cursor.com> * feat: launch Jaeger and enable OTEL with make container Use the observability compose profile in the container target and pass OTEL env vars so traces export to Jaeger. Dev targets keep OTEL disabled by default. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
…e image (redhat-data-and-ai#85) * refactor: consolidate Containerfile and Containerfile.base into single image Use one Containerfile for local compose and GHCR production builds with config_loader entrypoint and optional runtime config volume overrides. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden make local .env bootstrap and aegra dev invocation Avoid failing when .env.example is missing and invoke aegra directly from the venv without a subshell activate wrapper. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: simplify dev layout and fix local runtime issues Update README for make install/local workflow and current LangGraph API. Move deployment values under config/agent/deployment, consolidate container entrypoint into deep_agent/aegra/entrypoint.py, and remove obsolete scripts and test_otel_alert.py. Fix JSONC parsing for mcp.json URLs, ensure Ctrl+C exits after graceful shutdown, and improve make clean/container signal handling. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: address PR redhat-data-and-ai#85 review findings Replace os._exit(0) with sys.exit(0) in shutdown handler to allow proper Python cleanup (atexit, finally blocks, buffer flush). Add stale container cleanup in make local for old demo-* naming. Add test coverage for JSONC parser escaped quotes and entrypoint config validation. --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Add allowed_tools, denied_tools, and tool_approval config in subagent frontmatter. Enforce at MCP tool bind time with deny-wins-over-allow semantics. Compiled subagents get their own interrupt_on for tool_approval. Default subagents with tool_approval raise a validation error (must use compiled type). Backward compat: tools field auto-migrates to allowed_tools. - Rename tools → allowed_tools in frontmatter (with deprecation shim) - Add denied_tools filtering at subagent build time - Add tool_approval → interrupt_on for compiled subagents - Validation: async/default subagents reject tool_approval - 52 unit tests, isolation and precedence verified
…-ai#79) * feat: audit log opt in for tool, agent, subagent, memory * fix: folder flattened * fix: set audit and config reload from disk as true --------- Co-authored-by: SrichandVishnu <sris@redhat.com>
…redhat-data-and-ai#96) - Remove unused variables in subagents.py (ruff F841) - Add explicit type annotation for scrubbed dict in emitter.py (mypy) - Wrap isoformat() return with str() to satisfy mypy no-any-return - Add missing docstrings to AuditMiddleware methods (pydocstyle D102/D107) - Apply ruff format auto-fixes across 7 files Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a DynamicToolMiddleware that injects an execute_code tool into the agent and routes calls to ephemeral K8s Jobs for sandboxed code execution. Components: - CodeExecutionConfig: Pydantic model for images, resources, timeouts - K8sJobRunner: Job lifecycle (create, wait, logs, cleanup) with full security context (non-root, read-only FS, no SA token, seccomp) - CodeExecutionMiddleware: AgentMiddleware with tool injection via awrap_model_call and K8s routing via awrap_tool_call - CodeExecutionMetrics: 4-layer observability (OTEL metrics, tracing, audit events, structured logs) Supports Python, shell, and Node.js with configurable images, resource limits, and timeouts. Jobs auto-delete via ttlSecondsAfterFinished + explicit cleanup in finally block. 35 unit tests covering config validation, Job manifest generation, security fields, language mapping, status parsing, tool injection, routing, and observability.
…s logs - parse_container_status: check exit_code=0 for success regardless of termination reason (K8s sets reason='Completed' on success) - _collect_logs: decode bytes responses from K8s pod log API - _wait_for_pod: only return on Succeeded/Failed (not Running) - _get_exit_info: fall back to pod phase when container status unavailable - Add test for exit_code=0 with reason='Completed' Verified with live K8s Jobs on Kind cluster: 4/4 scenarios pass.
- Switch metrics/k8s_job_runner to stdlib logging with explicit StreamHandler(stderr) for reliable output inside LangGraph graph execution context where structlog's cached proxy doesn't reach stdout - Add code execution section to PROMPT.md so the LLM automatically uses execute_code for computation tasks without explicit instruction - Keep code_execution.enabled default as false (opt-in per deployment)
Phase 2 features for CodeExecutionMiddleware: 1. Custom Images — python-ds, python-ml domain variants via config 2. Network Access Control — per-execution NetworkPolicy (deny/allow/per_execution) 3. Execution Queuing — per-org asyncio.Semaphore with concurrency + timeout 4. File I/O — ConfigMap input at /input, emptyDir /output volume 5. Cost Tracking — OTEL metrics for cpu_seconds, memory_mb_seconds 6. Streaming — real-time stdout/stderr via follow=True with callback 59 unit tests covering all features.
…execution Tell the orchestrator that execute_code is the ONE exception to the delegation rule — it should call execute_code itself for computation and visualization, while still delegating domain work to subagents.
… designed output channel
1. NetworkPolicy egress=[] treated as falsy → None → allows ALL egress instead of blocking. Fixed: pass egress list directly. 2. Semaphore released in finally even when acquire() timed out → over-release breaks concurrency limit. Fixed: track acquired flag. 3. NetworkPolicy not created in deny mode → pods get unrestricted network by default. Fixed: always create NetworkPolicy. 4. ConfigMap/NetworkPolicy created outside try block → leaked on manifest build failure. Fixed: moved inside try/finally. 5. Streaming for-loop blocks event loop synchronously. Fixed: wrapped in asyncio.to_thread. 6. Empty code string passes validation. Fixed: reject code.strip()==''. 7. Unused duration variable in exception handler. Fixed: pass to log_failed for failed execution timing.
…e for NetworkPolicy
- Wire StreamWriter callback in middleware → runner for real-time code execution output via LangGraph custom stream events - Enable streaming_enabled and code_execution.enabled by default - Add execute_code to HITL exclude list (no approval needed) - Fix prompt: remove python-ds/python-ml references, add BMI fallback when analyst subagent is unavailable - Remove placeholder domain images from config defaults
1. OTEL Metrics — 9 instruments on MetricsContainer in otel.py 2. OTEL Tracing — trace_span() with active context for trace_id 3. Audit Events — emitter.py + context.py with sensitive key redaction 4. Scheduling Latency — measured and recorded via OTEL + logs 5. Image + namespace in log events 67 unit tests. Dashboard script at scripts/code-exec-dashboard.sh.
Took deep-agent's audit/* (PR redhat-data-and-ai#79 — more complete with buffer, config, resolve_trace_id), subagents.py, and PROMPT.md (headless worker section). Added CODE_EXECUTION to events.py. Inserted code execution section into merged PROMPT.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
What does this MR do and why?
Implements CodeExecutionMiddleware — a deepagents AgentMiddleware that injects an execute_code tool into the agent and
routes calls to ephemeral K8s Jobs for sandboxed code execution. Agents can now generate Python/shell/Node code, execute it
in an isolated container, and receive stdout/stderr back — with full observability, security enforcement, and automatic
cleanup.
Changes
Core middleware (deep_agent/src/code_execution/middleware.py): CodeExecutionMiddleware(AgentMiddleware) injects execute_code
tool via awrap_model_call, intercepts calls in awrap_tool_call, validates input (language, code length, empty code, file
size), manages per-org concurrency via asyncio.Semaphore, routes to K8sJobRunner, and returns structured ToolMessage with
stdout/stderr/exit_code.
K8s Job runner (deep_agent/src/code_execution/k8s_job_runner.py): Manages the full ephemeral Job lifecycle — manifest
generation with full pod security context (non-root, read-only FS, no SA token, seccomp, drop all caps), pod polling, log
collection (post-completion + streaming via follow=True), container status parsing (success/failed/timeout/OOM), cost
tracking via K8s Metrics API, NetworkPolicy creation/deletion per execution, ConfigMap-based file I/O, and cleanup in
finally block.
Configuration (deep_agent/src/code_execution/config.py): Pydantic model with configurable images, resource limits, timeout
(5-300s, default 60s), network access control (deny/allow_internet/per_execution), execution queuing (max concurrent per
org, queue timeout), cost tracking, streaming, and file I/O limits.
Observability (deep_agent/src/code_execution/metrics.py): 4-layer observability using stdlib logging (to ensure output
inside LangGraph graph-execution context): structured JSON logs for all lifecycle events, OTEL metric recording, OTEL
tracing spans, and platform audit event emission.
Wiring (deep_agent/src/infrastructure/middleware.py, deep_agent/src/agent/config/middleware.py): Registered in
build_middleware_list() following the existing middleware builder pattern. Config loaded from code_execution: section in
agent.yaml.
SSE streaming: Middleware wires LangGraph StreamWriter callback to K8sJobRunner.on_output for real-time code output
streaming through Aegra → BFF → UI.
Prompt updates (config/agent/PROMPT.md): Added code execution guidance — agent uses execute_code automatically for
computation tasks, with fallback for BMI when analyst subagent is unavailable.
Design spec with Mermaid diagrams covering architecture, before/after, platform integration, request flow, security constraints, K8s Job spec, observability design (4 layers), error handling taxonomy, alternatives analysis, imageconfiguration flow, and ephemeral pod observability guide.
AI Disclosure
AI used: Yes
Tool(s): None
Scope: Full implementation — design spec, middleware, K8s runner, config, metrics, tests, prompt engineering, bug fixes from code review
Human verification: Tested end-to-end on Kind cluster — Python/shell execution, file I/O via ConfigMap, NetworkPolicy
creation/deletion, execution queuing, cost tracking metrics, log streaming. 59 unit tests passing. All pre-commit hooks
green (ruff, mypy, pydocstyle, bandit).
Checklist
Deployment & Security Impact
Deployment: Code execution enabled by default (code_execution.enabled: true). Requires kubernetes Python package (imported lazily — no crash if missing, just runtime error on first execute_code call). Agent pod ServiceAccount needs RBAC Role for batch/jobs (create/get/delete), pods (get/list), pods/log (get), networking.k8s.io/networkpolicies (create/delete) in its namespace. No DB migrations.
Security: Execution pods run with runAsNonRoot, readOnlyRootFilesystem, automountServiceAccountToken: false,
capabilities.drop: [ALL], seccompProfile: RuntimeDefault. NetworkPolicy created per execution to control egress. Code
content never logged — only SHA-256 hash in audit events. Resource limits enforced via K8s resources.limits. Timeout
enforced via activeDeadlineSeconds + client-side asyncio.wait_for.
Reviewer Notes
Focus on:
created inside try block and cleaned up in finally.
in deny mode).